home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 5 / Apprentice-Release5.iso / Source Code / Libraries / VideoToolbox 96.06.15 / VideoToolboxSources / Luminance.doc < prev    next >
Text File  |  1995-09-26  |  24KB  |  482 lines

  1. #if 0
  2. Luminance.doc
  3. This file documents Luminance.c.
  4.  
  5. HISTORY:
  6. 3/11/92 dgp added prototypes to supplement the explanations of the routines.
  7. 3/24/94    dgp added LToE, LToEOrdered, and EToL.
  8. 10/17/94 dgp updated, in response to queries by Lan Zhang.
  9.  
  10. INTRODUCTION:
  11.  
  12. The purpose of these subroutines is accurate control of contrast of visual 
  13. stimuli for vision experiments. These subroutines set up the Color Lookup Table 
  14. (CLUT) of a video framestore on the Mac II (e.g. the Apple Mac II Color Video 
  15. Card) to properly drive a monochrome monitor (e.g. the Apple High Resolution 
  16. Monochrome Monitor) via an Institute for Sensory Research (ISR) Video 
  17. Attenuator. The ISR Video Attenuator consists of resistive dividers which 
  18. attenuate the three color signal from the video framestore by different 
  19. amounts and combine them to produce one monochrome signal.
  20.  
  21. The video attenuator and the theory behind the algorithms described here are
  22. the topic of a paper:
  23. D.G. Pelli and L. Zhang (1991) Accurate control of contrast on microcomputer displays. 
  24. Vision Research, 31:1337-1360.
  25.  
  26. Achieving the goal of accurate control of contrast required solving five 
  27. problems:
  28. 1. The luminance of the monitor is nonlinearly related to the input voltage
  29. which depends linearly on the triplet loaded into the CLUT. This is solved by
  30. allowing the user to provide a polynomial (I recommend 4th order) describing the
  31. nonlinear relationship between luminance and nominal voltage. (For computational
  32. reasons, you must also supply a quadratic fit.) The subroutines LToV and VToL
  33. use this polynomial to convert back and forth.
  34. 2. Affordable framestores for microcomputers have 8 bit Digital to Analog
  35. Converters (DACs), which can only represent 256 different luminances. This is
  36. not enough precision to produce threshold-contrast patterns, which are important
  37. for vision experiments. This is solved by the ISR Video Attenuator which
  38. combines the outputs of the three DACs ("red", "green", and "blue") with various
  39. attenuations to yield one monochrome signal which can represent low contrasts
  40. (by varying an attenuated DAC) and high contrasts (by varying an unattenuated
  41. DAC).
  42. 3. DACs are only accurate to ± a least significant bit (LSB). The Brooktree DACs
  43. used by Apple and RasterOps are specified to have a ±1 LSB "integrated
  44. linearity" error, which means that the voltage produced by any number will
  45. deviate from a line connecting the 0 and 255 points by at most 1 LSB, which is
  46. defined as 1/255 times the voltage difference between 0 and 255. I assume a
  47. slightly stronger restriction will hold, that the difference between any two
  48. settings will be in error by at most one LSB. So it is important to allow the
  49. user to specify what range of luminances will be used to present a particular
  50. stimulus and to fix the coarsest DACs (i.e. those with the least attenuation in
  51. the ISR Video Attenuator) and only vary the fine DACs to produce the specified
  52. luminances. The range setting is done by SetLuminanceRange() which is called
  53. automatically by SetLuminance() and SetLuminances().
  54. 4. Apple's software environment for graphics, QuickDraw, isn't designed for
  55. vision experiments. The facilities for creating images are fine, but the control
  56. of the CLUTs provided by the Palette and Color Managers is unsatisfactory for
  57. vision research because a lot happens behind your back. In particular the
  58. Palette Manager likes to maintain a consistent color environment across the
  59. entire desktop, which includes all your screens. Well, in vision experiments we
  60. generally want to treat each screen as a totally separate device, unaffected by
  61. what we do to the other screens. This is solved by the routine GDSetEntries()
  62. which makes a low-level call to the video driver to change the CLUT directly
  63. without QuickDraw's knowledge. (This will work with any video card that works
  64. with the Mac II.) The routine LoadLuminances(), which is used below, is just a
  65. convenient glue routine for calling GDSetEntries().
  66. 5. Apple specifies that the video driver should silently implement gamma
  67. correction to attempt to correct for the display's nonlinear relation between
  68. luminance and input voltage. We don't want this because this hidden gamma
  69. correction loses precision and interferes with the operation of the ISR Video
  70. Attenuator, which will seem to operate NONlinearly if this gamma correction
  71. takes place unbeknownst to us. This is solved by the routine GDLinearGamma()
  72. which makes a low-level call to the video driver and loads a linear gamma table,
  73. i.e. no correction.
  74.  
  75. SUMMARY:
  76.  
  77. double EToL(LuminanceRecord *LP,int entry);
  78. Returns the luminance associated with the particular entry.
  79.  
  80. int LToE(LuminanceRecord *LP,double L,int firstEntry,int lastEntry);
  81. Returns the index of the table entry in specified range with luminance closest to L.
  82.  
  83. int LtoEOrdered(LuminanceRecord *LP,double L,int firstEntry,int lastEntry);
  84. Returns the index of the table entry in specified range with luminance closest to L.
  85. Runs fast by assuming an ordered table from firstEntry to lastEntry, either 
  86. increasing or decreasing.
  87.  
  88. double SetLuminance(GDHandle device,LuminanceRecord *LP
  89.     ,int entry,double luminance
  90.     ,double lowLuminance,double highLuminance)
  91. Set one entry in the ColorSpec table (and the CLUT if device is not NULL) to
  92. a specified luminance. It's ok for lowLuminance to be greater than highLuminance.
  93. SetLuminance() sets a single entry to the specified luminance. You must also
  94. indicate the luminance range that you are working over, to allow optimal choice
  95. of which dacs to fix, etc., to yield minimum error in relative luminance. 
  96. SetLuminances() does its work by making a call to SetLuminance() for each entry.
  97. SetLuminance takes 1 ms to set up the range (by calling SetLuminanceRange), which
  98. it only has to do once. Repeated calls to SetLuminance with the same range will
  99. not cause re-computation of the range. Once the range is set, SetLuminance takes
  100. about 0.1 ms. If SetLuminance is too slow for a real-time application you can tell
  101. it to just compute, but not load the new ColorSpec table (just supply NULL in place
  102. of device), and you can then quickly load your ColorSpec table into the CLUT
  103. later by calling LoadLuminances().
  104.  
  105. double SetLuminancesAndRange(GDHandle device,LuminanceRecord *LP
  106.     ,int firstEntry,int lastEntry
  107.     ,double firstLuminance,double lastLuminance
  108.     ,double lowLuminance,double highLuminance)
  109. /*
  110. Set a series of entries in the ColorSpec table (and the CLUT if device is not NULL)
  111. to a linear sequence of luminances. 
  112. Uses last two arguments to set the luminance range of interest.
  113. */
  114. double SetLuminances(GDHandle device,LuminanceRecord *LP
  115.     ,int firstEntry,int lastEntry
  116.     ,double firstLuminance,double lastLuminance)
  117. /*
  118. Set a series of entries in the ColorSpec table (and the CLUT if device is not NULL)
  119. to a linear sequence of luminances. Assume this is the entire luminance range of 
  120. interest.
  121. */
  122. SetLuminances() and SetLuminancesAndRange() both produce a linear relationship
  123. between CLUT entry and luminance over the range firstLuminance to lastLuminance,
  124. with minimum error relative to any luminance in the range lowLuminance to
  125. highLuminance. (We don't care about a small error in mean luminance.)
  126. (SetLuminances, which doesn't have these arguments, uses firstLuminance and
  127. lastLuminance to set this range.) The three DACs are linear, and the video
  128. attenuator will combine them linearly yielding a voltage V at the display input.
  129. This allows you to compute your image (e.g. a sinewave grating) and loaded to
  130. the frame store as a full scale linear function, i.e. with values ranging
  131. 0..255.  Linearization of the display and setting of contrast to whatever you
  132. want are both accomplished by one call to SetLuminances(). The contrast can be
  133. changed by calling SetLuminances() again, now with a larger or smaller luminance
  134. range. If SetLuminances is too slow for a real-time application (its
  135. computations take about 8-30 ms to compute a whole 256-entry CLUT) you can tell
  136. it to just compute, but not load the new ColorSpec table (just supply NULL in
  137. place of device), and you can then quickly load your ColorSpec table into
  138. the CLUT later by calling LoadLuminances().
  139.  
  140. double GetLuminance(GDHandle device,LuminanceRecord *LP,int entry)
  141. /*
  142. If device is not NULL then examines one entry in the actual CLUT, otherwise
  143. examines the ColorSpec table contained in *LP, and in either case returns the
  144. luminance that will be produced. Supplying an illegal entry value results in a
  145. returned value of -INF.
  146. */
  147. GetLuminance() returns the luminance of a single entry. If device is not NULL then
  148. GetLuminance will make a low-level call to the video driver to determine what is in that
  149. CLUT entry in the actual hardware, and will return the luminance that is expected
  150. to result given the channel gains and luminance nonlinearity described in your
  151. LuminanceRecord. If device is NULL then GetLuminance
  152. returns the luminance corresponding to the entry in the ColorSpec table in your
  153. LuminanceRecord.
  154.  
  155. void IncrementLuminance(GDHandle device,LuminanceRecord *LP,int entry)
  156. /*
  157. Make smallest possible increase of the luminance of one entry in the ColorSpec table.
  158. This is a way to figure out what is the lowest contrast that you can produce.
  159. */
  160.  
  161. void LoadLuminances(GDHandle device, LuminanceRecord *LP,
  162.     int firstEntry, int lastEntry)
  163. /*
  164. This just calls GDSetEntries() to load your ColorSpec table into the CLUT of
  165. your screen device. It is here simply to provide a cosmetic match to the
  166. call to SetLuminances(), for which loading the CLUT is optional.
  167. Note: if you prefer, instead of LP you may send just the address of 
  168. a ColorSpec table, cast to (LuminanceRecord *), since a LuminanceRecord 
  169. begins with a ColorSpec table.
  170. Does nothing if device==NULL or the device has no driver.
  171. */
  172.  
  173. double GetV(GDHandle device,LuminanceRecord *LP,int entry);
  174. double VToL(LuminanceRecord *LP,double V);
  175. double LToV(LuminanceRecord *LP,double L);
  176. double LToL(LuminanceRecord *LP,double L);
  177. Most users will never have any reason to use these routines. They are useful
  178. primarily in error analysis of the Luminance.c package.
  179.  
  180. EXAMPLES:
  181.  
  182. double tolerance,luminance,meanLuminance,c;
  183. int firstEntry,lastEntry,entry;
  184.  
  185. /* load CLUT with linear luminance range, immediately */
  186. tolerance=SetLuminances(device,&LR,0,255,(1.-c)*meanLuminance,(1+c)*meanLuminance);
  187.  
  188. /* load ColorSpec table with linear luminance range, then load CLUT */
  189. tolerance=SetLuminances(NULL,&LR,0,255,(1.-c)*meanLuminance,(1+c)*meanLuminance);
  190. LoadLuminances(device,&LR,firstEntry,lastEntry);
  191.  
  192. /* load ColorSpec table with sinusoidal luminance range, then load CLUT */
  193. for(entry=0;entry<256;entry++) {
  194.     luminance = meanLuminance*(1.0+c*sin(2.0*3.14159265*entry/256.));
  195.     tolerance=SetLuminance(NULL,&LR,entry,luminance,(1.-c)*meanLuminance,(1+c)*meanLuminance);
  196. }
  197. LoadLuminances(device,&LR,firstEntry,lastEntry);
  198.  
  199. /* Examine an entry in the ColorSpec table or CLUT */
  200. luminance=GetLuminance(device,&LR,entry);    /* in CLUT */
  201. luminance=GetLuminance(NULL,&LR,entry);        /* in ColorSpec table */
  202.  
  203. NOTES:
  204.  
  205. The argument firstEntry must not exceed lastEntry. They may be equal.
  206.  
  207. The returned value, tolerance, is an estimate of the largest possible error in
  208. the luminance DIFFERENCES, taking into account the precision, accuracy (assumed
  209. to be ±1 least significant bit), and range of the DACs. (Note the returned
  210. tolerance tells you about errors in the luminance DIFFERENCES on the display.
  211. The error in ABSOLUTE luminance of the display is expected to be at most ±1 step
  212. of all the DACs, i.e. one part in 255 of full scale.)
  213.  
  214. There are no restrictions at all on firstLuminance and lastLuminance. It is
  215. reasonable to request an impossibly large luminance range, e.g. from a negative
  216. luminance up to twice the maximum luminance. SetLuminances will produce the
  217. closest possible approximation. The luminance of each entry will be clipped to
  218. fit in the range of possible luminances. Thus you will obtain the requested
  219. contrast for pixel values that aren't clipped. You can detect the clipping by
  220. the fact that the returned tolerance will be very large.
  221.  
  222. A common mistake in C programming is to use a pointer that supposedly points to
  223. a structure, without ever allocating said structure. Consider the
  224. LuminanceRecord. The following declaration and call will be accepted by the
  225. compiler, but will usually have disastrous consequences:
  226.     LuminanceRecord *LP;
  227.     SetLuminances(...,LP,...);        /* Bad! */
  228. The problem is that you never allocated a LuminanceRecord, only a pointer. When
  229. SetLuminances starts storing information in what it thinks is a LuminanceRecord it
  230. will actually be writing over a random part of memory. What you should do is:
  231.     LuminanceRecord LR;
  232.     SetLuminances(...,&LR,...);        /* Good */
  233. Or you can do this:
  234.     LuminanceRecord LR,*LP;
  235.     LP=&LR;
  236.     SetLuminances(...,LP,...);        /* Good */
  237. This second technique is a better habit, since if you want to put some of your code into
  238. a subroutine, you'll be passing the pointer, not the structure itself, so you might as well
  239. get used to using the pointer.
  240.  
  241. Before using SetLuminance or SetLuminances you must initialize your LuminanceRecord. 
  242. You should do that by reading in,
  243.     LuminanceRecord LR;
  244.     ReadLuminanceRecord("LuminanceRecord2.h",&LR,0);
  245. or #including a file with all the parameters describing your monitor. 
  246.     LuminanceRecord LR;
  247.     #include "LuminanceRecord2.h"
  248.  
  249. Here's an example of the contents of a LuminanceRecord1.h file, as produced by the
  250. program CalibrateLuminance. You must use CalibrateLuminance to calibrate
  251. your own framestore, ISR Video Attenuator, and display. 
  252.     LR.screen=1;    /* device=GetScreenDevice(LR.screen); */
  253.     /* Caution: the screen number used here and in GetScreen Device is NOT the same as */
  254.     /* displayed by the Monitors cdev in the Control Panel. Sorry. The most obvious difference */
  255.     /* is that GetScreenDevice always assigns 0 to the main screen, the one with the menu bar. */
  256.     LR.date="3:09 PM Friday, October 16, 1992";
  257.     LR.id="5111767";
  258.     LR.name="signal";
  259.     LR.notes="manoj, lights off, photometer 1 inch from screen";
  260.     LR.dpi=76.0;    /* pixels per inch */
  261.     LR.Hz=66.67;    /* frames per second */
  262.     LR.units="cd/m^2";
  263.     /* coefficients of polynomial fit */
  264.     LR.coefficients=9;    /* # of coefficients in polynomial fit */
  265.     /* L(V)=p[0]+p[1]*V+p[2]*V*V+ . . . ±polynomialError */
  266.     LR.p[0]=1.28201e-14;
  267.     LR.p[1]=8.25776e-13;
  268.     LR.p[2]=5.00064e-11;
  269.     LR.p[3]=2.51138e-09;
  270.     LR.p[4]=7.98055e-08;
  271.     LR.p[5]=-2.04184e-09;
  272.     LR.p[6]=1.79181e-11;
  273.     LR.p[7]=-6.3791e-14;
  274.     LR.p[8]=8.10888e-17;
  275.     LR.polynomialError=  0.2263;    /* RMS error of fit */
  276.     /* coefficients of quadratic fit */
  277.     /* L(V)=q[0]+q[1]*V+q[2]*V*V±quadraticError */
  278.     LR.q[0]=4.86285;
  279.     LR.q[1]=-0.201266;
  280.     LR.q[2]=0.00125998;
  281.     LR.quadraticError=  2.5493;    /* RMS error of fit */
  282.     /* coefficients of power law fit */
  283.     /* L(V)=power[0]+Rectify(power[1]+power[2]*V)^power[3]±powerError */
  284.     /* where Rectify(x)=x if x≥0, and Rectify(x)=0 otherwise */
  285.     /* Pelli & Zhang (1991) Eqs.9&10 use symbols v=V/255, alpha=power[0], beta=power[1], kappa=power[2]*255, gamma=power[3] */
  286.     LR.power[0]=-0.0615421;
  287.     LR.power[1]=-3.38554;
  288.     LR.power[2]=0.0305017;
  289.     LR.power[3]=2.48866;
  290.     LR.powerError=  0.2616;    /* RMS error of fit */
  291.     /* coefficients of power law fit, with fixed exponent */
  292.     /* L(V)=fixedPower[0]+Rectify(fixedPower[1]+fixedPower[2]*V)^fixedPower[3]±fixedPowerError */
  293.     LR.fixedPower[0]=-0.764058;
  294.     LR.fixedPower[1]=-2.96785;
  295.     LR.fixedPower[2]=0.0310164;
  296.     LR.fixedPower[3]=  2.28;
  297.     LR.fixedPowerError=  1.1986;    /* RMS error of fit */
  298.     LR.r=0.0282406;
  299.     LR.g=0.149883;
  300.     LR.b=0.821877;
  301.     LR.gainAccuracy=-0.0337905;
  302.     LR.gm=3.85543;    /* The monitor's contrast gain. */
  303.     LR.VMin=  0;    /* minimum value that can be loaded into DAC */
  304.     LR.VMax=255;    /* maximum value that can be loaded into DAC */
  305.     LR.LMin=   -0.06;    /* luminance at VMin */
  306.     LR.LMax=   39.70;    /* luminance at VMax */
  307.     LR.LBackground=   9.731;    /* background luminance during calibration */
  308.     LR.VBackground=193;    /* background number used during calibration */
  309.     LR.rangeSet=0;    /* indicate that range parameters have yet to be set */
  310.     LR.L.exists=0;    /* indicate that luminance table has yet to be initialized */
  311. Note that these parameters describe fits of three functions to the gamma function.
  312. You may omit any or all of these fits, but should document it by setting the
  313. corresponding error terms to infinity (=1.0/0.0). If you omit all of them then
  314. you must supply a table describing the gamma function, e.g.
  315.     LR.L._VMin=DoubleToFix(0.);    /* if possible, restrict to just the monotonic range */
  316.     LR.L._VMax=DoubleToFix(255.);    /* if possible, restrict to just the monotonic range */
  317.     LR.L._dV=(LR.L._VMax-LR.L._VMin)/(LUMINANCES_IN_TABLE-1);
  318.     LR.L.latestIndex=-1;            /* invalid latestIndex, so hunt will start from scratch */
  319.     LR.L.exists=luminanceSet;        /* mark table as valid */
  320.     LR.L._Lu[0]=DoubleToFix(0.57);
  321.     LR.L._Lu[1]=DoubleToFix(0.57);
  322.     ...
  323.     LR.L._Lu[LUMINANCES_IN_TABLE-1]=DoubleToFix(101.13);
  324. "LUMINANCES_IN_TABLE" is currently defined to be 128 in Luminance.h. Normally this 
  325. table is synthesized at run time, from one of the formulaic descriptions,
  326. but you may prefer to supply it directly, possibly using the raw measurements,
  327. and not bother with any fitting. The formulas are not used if the table is supplied.
  328.  
  329. To fill in the numbers above, you have to do two calibrations. (This is what the
  330. CalibrateLuminance program does.)
  331.  
  332. 0. Before starting, call GDLinearGamma(). (Note that GDOpenWindow 
  333. automatically calls GDLinearGamma for you.) All calibrations should be done
  334. with the ISR Video Attenuator in place.
  335.  
  336. 1. Measure the luminance versus "voltage" nonlinearity of your monitor.
  337. I use a separate program "CalibrateLuminance" to measure the screen luminance at values
  338. of V from 0 to 255. For this calibration you load equal values into Red, Green, and Blue
  339. lookup tables. I use quadratic, polynomial, and power law fits. See Pelli & Zhang (1991).
  340.  
  341. You must set LR.LMin and LR.LMax to the luminances at LR.VMin and LR.VMax.
  342.  
  343. The following program fragment allows you to measure the screen luminance as a function
  344. of nominal voltage.
  345.  
  346. #include "VideoToolbox.h"
  347. #include "Luminance.h"
  348.  
  349. void main(void)
  350. {
  351.     LuminanceRecord LR;
  352.     int V,i,done;
  353.     double a;
  354.     GDHandle oldDevice,device;
  355.     CWindowPtr cWindow,oldCWindow;
  356.     char string[32];
  357.     
  358.     #include "LuminanceRecord1.h"
  359.     GetGWorld(&oldCWindow,&oldDevice);
  360.     LR.screen=1;
  361.     device=GetScreenDevice(LR.screen);    /* screen 1 */
  362.     cWindow=GDOpenWindow(device);        /* Open a full-screen window with explicit colors */
  363.     SetGWorld(cWindow,device);
  364.     PmBackColor(1);                        /* pick a color table entry */
  365.     EraseRect(&cWindow->portRect);        /* fill whole window with that color */
  366.     SetGWorld(oldCWindow,oldDevice);
  367.     LR.L._VMin=DoubleToFix(0.);            /* if possible, restrict to just the strictly monotonic range */
  368.     LR.L._VMax=DoubleToFix(255.);
  369.     done=0;
  370.     for(i=0;i<LUMINANCES_IN_TABLE;i++){
  371.         LR.L._dV=ceil(FixToDouble(LR.L._VMax-LR.L._VMin)/(LUMINANCES_IN_TABLE-1));
  372.         V=FixToLong(i*LR.L._dV);
  373.         if(V>=FixToLong(LR.L._VMax)){
  374.             V=FixToLong(LR.L._VMax);
  375.             done=1;
  376.         }
  377.         LR.table[1].rgb.red        =V<<8;    /* Set color table entry 1. */
  378.         LR.table[1].rgb.green    =V<<8;
  379.         LR.table[1].rgb.blue    =V<<8;
  380.         LoadLuminances(device,&LR,1,1);    /* Copy color table entry 1 to CLUT */
  381.         printf("%3d Please measure screen luminance now, in %s, and type in:",V,LR.units);
  382.         gets(string);
  383.         sscanf(string,"%lf",&a);
  384.         printf("%6.2g %s\n",a,LR.units);
  385.         LR.L._Lu[i]=DoubleToFix(a);
  386.         if(done)break;
  387.         if(LR.L._Lu[i]<=LR.L._Lu[0]){    /* restrict to just the strictly monotonic range */
  388.             LR.L._VMin=LongToFix(V);
  389.             LR.L._Lu[0]=LR.L._Lu[i];
  390.             i=0;
  391.         }
  392.     }
  393.     LR.L.latestIndex=-1;            /* invalid latestIndex, so hunt will start from scratch */
  394.     LR.L.exists=luminanceSet;        /* mark table as valid */
  395. }
  396.  
  397. 2. Measure the gains of the three inputs of your ISR Video Attenuator. You could
  398. do this by using an oscilloscope to measuring the voltage at the input to the monitor.
  399. Instead, I measure the resulting luminance and use LToV() to infer the voltage.
  400. Vary one DAC while holding the other two fixed at 255 and measure the luminances. 
  401. Use the function LToV() to convert back to a "voltage" and compute the gain of the
  402. varying DAC. Note that the three DACs on your framestore will generally have gains that
  403. match to only ±5%. This calibration is measuring the overall gain of each of the three
  404. pathways, including your DACs and the ISR Video Attenuator.    
  405.  
  406. LIMITATIONS:
  407.  
  408. It is imperative that you call GDLinearGamma() at some time before using
  409. SetLuminances. SetLuminances ASSUMES that no gamma correction takes place. You
  410. only need to call GDLinearGamma once. It will stay that way until the next time
  411. you restart your computer. Incidentally, GDOpenWindow() calls GDLinearGamma for
  412. you.
  413.  
  414. The luminance calibration data that you supply to SetLuminances must also have
  415. been collected with no gamma correction, i.e. AFTER calling GDLinearGamma.
  416.  
  417. The reason that gamma correction is not allowed is that it would result in a
  418. nonlinear transformation of the three channels BEFORE they are combined in the
  419. Video Attenuator.
  420.  
  421. The measurement of the gains of the three pathways must be made using YOUR
  422. framestore. The gains of your three DACs will in general be different from each
  423. other, and different from framestore to framestore. Brooktree guarantees the
  424. matching of the gains of the three DACs on their chip to only ±5%.
  425.  
  426. The luminance record may include either a table of luminance calibrations. If it
  427. is not supplied then it will be synthesized from the parameters of the
  428. polynomial or power law fit. If the power, polynomial, or quadratic fit
  429. parameters are not supplied then the appropriate LR.powerError,
  430. LR.polynomialError, or LR.quadraticError field should be set to infinity
  431. (=0.0/1.0).
  432.             
  433. The whole package is at present restricted to grayscale.
  434. There is no provision for linearizing a color monitor. In particular it is
  435. assumed that the the DACs are linearly combined BEFORE the display nonlinearity.
  436. Linearizing luminance on a color display would need to allow for three different
  437. nonlinear transformations.
  438.  
  439. EXPLANATION OF THE CODE:
  440.  
  441. Physically your framestore transforms each CLUT entry number to a voltage that
  442. will be linearly related to the number. The three output voltages will be
  443. combined by the ISR Video Attenuator to produce a single voltage which drives
  444. the video monitor. Finally, the display nonlinearly transforms V to produce a
  445. luminance L. My convention for "measuring" the "voltage" V at the input of the
  446. monitor is that V=0 when all three DACs are set to zero and V=255 when all three
  447. DACs are set to 255. (This will be linearly related to volts measured, with a
  448. voltmeter, at the output of the Video Attenuator.) The virtue of the attenuator
  449. is that it allows us to produce nonintegral values of V.
  450.  
  451. There are two givens:
  452. 1. DACs are inaccurate. A good DAC may be specified to be merely monotonic. For
  453. purposes of computing the returned tolerance value, I follow the Brooktree DAC
  454. specification of ±1 LSB error in integrated linearity and assume that the error
  455. in any luminance difference is at most ±1 LSB.
  456. 2. We want to minimize the error in representing the waveform, but are not
  457. particularly worried about the exact value of the mean luminance. Thus, if we
  458. have DACs with different gains we may fix the coarse DACs to set the mean
  459. luminance and vary the fine DACs to produce the linear range of luminances
  460. requested.
  461.  
  462. Here's the strategy. We want to cover the range L0 to Ln with the smallest
  463. possible error in L-L0.
  464.  
  465. Steps 1 to 5 happen in SetLuminanceRange:
  466. 1. Sort the DACs by gain g, where gain is defined as the change in V when
  467. a single DAC is increased from 0 to 255, so g0+g1+g2=1. Let the gains be g0>g1>g2. 
  468. 2. Transform the luminance range to a nominal voltage range lowV and highV.
  469. 3. Decide which DACs should be variable and which should be fixed,
  470. so as the minimize the tolerance in the luminance increments.
  471. 4. Temporarily set the variable DACs to their midpoints. Now set the fixed DACs to
  472. most accurately represent the midpoint of the nominal voltage range, (lowV+highV)/2.
  473. 5. If necessary, compute a small luminance offset LShift to bring the requested range 
  474. lowV to highV into the range attainable by the variable DACs. (This is necessary
  475. because the centering in step 4 may not be precise enough.)
  476.  
  477. Step 6 happens in _SetLuminance:
  478. 6. Set the variable DACs in the ColorSpec entry so as to most accurately represent
  479. L+LShift.
  480.  
  481. SetLuminances(), SetLuminancesAndRange(), and SetLuminance() all call _SetLuminance().
  482. #endif